//
//  NetworkManager.swift
//  AG
//
//  Created by BirdMichael on 2020/4/27.
//  Copyright © 2020 BirdMichael. All rights reserved.
//

import BWExtensionKit
import Moya
import UIKit
import BWToolKit


public class Provider: MoyaProvider<Target> {
    private init(){}
    public init(target: Target) {
        let requestClosure =
        { (
            endpoint: Endpoint,
            done: @escaping MoyaProvider<Target>.RequestResultClosure
        ) in
            do {
                var request = try endpoint.urlRequest()
                request.timeoutInterval = target.request.timeouts
                request.cachePolicy = target.request.cachePolicy
                done(.success(request))
            }
            catch {
                return
            }
        }
        let networkPlugin = NetworkActivityPlugin(networkActivityClosure: { change, target in
            
        })
        
        super.init(requestClosure: requestClosure, callbackQueue: .main, session: .default, plugins: [
            networkPlugin
        ], trackInflights: false)
    }
}

extension Networker {
    public struct TaskHandler {
        var callback: Request.ResultingData?
        var progressing: Progressing?
        public init(callback: Request.ResultingData?, progressing: Progressing? = nil) {
            self.callback = callback
            self.progressing = progressing
        }
    }
    
    @discardableResult
    public static func send<API: RequestAPI>(
        apis: [API],
        queuePolicy: GCD.Task.QueuePolicy = .concurrent(maxCount: -1),
        timeoutPolicy: GCD.Task.TimeoutPolicy = .waiting,
        tasking: BlockTR<GCD.Task.Operation, TaskHandler>?,
        completion: BlockT<GCD.Task.ExecuteResult>?
    ) -> [GCD.Task.Operation]
    {
        let ops: [GCD.Task.Operation] = apis.map{ api in
            let op = GCD.Task.Operation(taskId: api.request.fullUrl, timeout: api.request.timeouts)
            let handler = tasking?(op)
            op.handler = { [weak op] in
                guard let strongOp = op else {
                    return
                }
                request(api: api, progressing: handler?.progressing, callback: { state in
                    switch state {
                    case .success, .failed:
                        strongOp.completionBlock?()
                    default: break
                    }
                    handler?.callback?(state)
                })
            }
            return op
        }
        GCD.Task.async(
            operations: ops,
            queuePolicy: queuePolicy,
            timeoutPolicy: timeoutPolicy,
            notify: .main,
            completion: completion ?? {_ in}
        )
        return ops
    }
    
    @discardableResult
    public static func send(
        requests: [Request],
        queuePolicy: GCD.Task.QueuePolicy = .concurrent(maxCount: -1),
        timeoutPolicy: GCD.Task.TimeoutPolicy = .waiting,
        tasking: BlockTR<GCD.Task.Operation, TaskHandler>?,
        completion: BlockT<GCD.Task.ExecuteResult>?
    ) -> [GCD.Task.Operation]
    {
        let ops: [GCD.Task.Operation] = requests.map{ req in
            let op = GCD.Task.Operation(taskId: req.fullUrl, timeout: req.timeouts)
            let handler = tasking?(op)
            op.handler = { [weak op] in
                guard let strongOp = op else {
                    return
                }
                send(request: req, progressing: handler?.progressing, callback: { state in
                    switch state {
                    case .success, .failed:
                        strongOp.completionBlock?()
                    default: break
                    }
                    handler?.callback?(state)
                })
            }
            return op
        }
        GCD.Task.async(
            operations: ops,
            queuePolicy: queuePolicy,
            timeoutPolicy: timeoutPolicy,
            notify: .main,
            completion: completion ?? {_ in}
        )
        return ops
    }
}

public enum Networker {
    public typealias Progressing = BlockTT<Double, Progress?>
    
    @discardableResult
    public static func request(
        api: RequestAPI,
        progressing: Progressing? = .none,
        callback: Request.ResultingData?
    ) -> Request {
        return send(request: api.request, progressing: progressing, callback: callback)
    }
    
    @discardableResult
    public static func send(
        request: Request,
        progressing: Progressing? = .none,
        callback: Request.ResultingData?
    ) -> Request  {
        return _requesting(target: .init(request: request), progressing: progressing, callback: callback)
    }
    
    @discardableResult
    private static func _requesting(
        target: Target,
        progressing: Progressing? = .none,
        callback: Request.ResultingData?
    ) -> Request {
        let request = target.request
        
        request.status = .loading
        GCD.asyncInMainQueue {
            callback?(.loading(request: request))
        }
        print(
        """
            🙏🙏🙏🙏🙏🙏  Request Loading  🙏🙏🙏🙏🙏🙏\n
            | METHOD | \(target.request.method.rawValue)\n
            | HOST | \(target.request.host.string)\n
            | PATH | \(target.request.path)\n
            | PARAMS | \(target.request.params)\n
            ============== End ==============\n
        """)
        
        request.cancellToken =
        Provider(target: target)
            .request(
                target,
                callbackQueue: .main,
                progress: { progress in
                    progressing?(progress.progress, progress.progressObject)
                })
        { result in
            request.status = .finished
            GCD.asyncInMainQueue {
                callback?(.finished(request: request))
            }
            print(
                """
                   ✊✊✊✊✊✊  Request Finished  ✊✊✊✊✊✊ \n
                    | URL | \(target.request.fullUrl)\n
                    | HEADERS | \(target.request.headers)\n
                    ============== End ==============\n
                """
            )
            switch result {
            case .success(let response):
                print("""
                        😘😘😘😘😘😘  Request Success  😘😘😘😘😘😘\n
                        | Data | \(response.data)\n
                        ============== End ==============\n
                    """)
                request.status = .success
                GCD.asyncInMainQueue {
                    callback?(.success(data: response.data))
                }
            case .failure(let error):
                print("""
                        😭😭😭😭😭😭  Request Failed  😭😭😭😭😭😭 \n
                        | Error | \(error.localizedDescription)\n
                        ============== End ==============\n
                    """)
                request.status = .failed(with: .responseError(error))
                GCD.asyncInMainQueue {
                    callback?(.failed(error: .responseError(error)))
                }
            }
        }
        return request
    }
}
